#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/time.h>

#include "imbus_internal.h"
#include "im_value.h"
#include "im_value_array.h"
#include "im_event.h"

/************************** Declaration of internal functions ***************/
static void     __im_event_initialize   (IMPointer       event);
static void     __im_event_finalize     (IMPointer       event);
static void     __im_event_copy         (IMPointer       dest,
                                         IMConstPointer  src);
static IMBool   __im_event_serialize    (IMConstPointer  obj,
                                         IMPointer       stream);
static IMBool   __im_event_deserialize  (IMPointer       obj,
                                         IMPointer       stream);
static void     __im_event_class_init   (IMPointer       klass);

/****************** Declaration of object and class structure ************/
struct _IMEvent
{
    IMObject object;

    /* private */
    IMUInt32       type;
    IMUInt32       source;
    IMUInt32       dest;
    IMUInt32       orig_dest;
    IMUInt32       icid;
    IMUInt64       timestamp;
    IMUInt32       serial;
    IMBool         observable;
    IMBool         reply;

    IMValueArray  *data;
};

struct _IMEventClass
{
    IMObjectClass parent_class;
};

/************************ Internal varable ************************/
static const IMObjectClass *__im_event_parent_class = 0;

/************* Implementation of internal functions ***************/

static void
__im_event_initialize (IMPointer event)
{
    ((IMEvent*)event)->type       = 0;
    ((IMEvent*)event)->source     = 0;
    ((IMEvent*)event)->dest       = IM_EVENT_DEST_DEFAULT;
    ((IMEvent*)event)->orig_dest  = IM_EVENT_DEST_DEFAULT;
    ((IMEvent*)event)->icid       = IM_EVENT_INPUT_CONTEXT_NONE;
    ((IMEvent*)event)->timestamp  = im_get_current_time_in_milliseconds ();
    ((IMEvent*)event)->serial     = 0;
    ((IMEvent*)event)->data       = 0;
    ((IMEvent*)event)->observable = TRUE;
    ((IMEvent*)event)->reply      = FALSE;
}

static void
__im_event_finalize (IMPointer event)
{
    IMEvent *ep = IM_EVENT (event);

    _im_assert (ep != 0);

    if (ep->data)
        im_object_unref (ep->data);
}

static void
__im_event_copy (IMPointer dest, IMConstPointer src)
{
    IMEvent       *dp = IM_EVENT (dest);
    const IMEvent *sp = IM_CONST_EVENT (src);

    _im_assert (dp && sp);

    dp->type        = sp->type;
    dp->source      = sp->source;
    dp->dest        = sp->dest;
    dp->orig_dest   = sp->orig_dest;
    dp->icid        = sp->icid;
    dp->timestamp   = sp->timestamp;
    dp->serial      = sp->serial;
    dp->observable  = sp->observable;
    dp->reply       = sp->reply;

    if (dp->data) im_object_unref (dp->data);

    /* Newly created IMValueArray should be owned by the event */
    dp->data        = sp->data ? im_object_ref (im_object_clone (sp->data)) : 0;

    /* Call copy method of parent class */
    __im_event_parent_class->copy (dest, src);
}

static IMBool
__im_event_serialize (IMConstPointer obj, IMPointer stream)
{
    const IMEvent *ep = IM_CONST_EVENT (obj);
    IMStream      *sp = IM_STREAM (stream);
    IMUInt32       buf [9];

    _im_assert (ep && sp);

    /* Call parent serialize method */
    if (!__im_event_parent_class->serialize (obj, stream))
        return FALSE;

    buf [0] = ep->type;
    buf [1] = ep->source;
    buf [2] = ep->dest;
    buf [3] = ep->orig_dest;
    buf [4] = ep->icid;
    buf [5] = (ep->timestamp & 0xFFFFFFFF);
    buf [6] = (ep->timestamp >> 32);
    buf [7] = ep->serial;
    buf [8] = (ep->observable ? 1 : 0);
    buf [8] |= (ep->reply ? 2 : 0);

    if (!im_stream_put_uint32_array (sp, buf, 9))
        return FALSE;

    if (ep->data)
        return im_stream_put_object (sp, ep->data);
    else
        return im_stream_put_void (sp);
}

static IMBool
__im_event_deserialize (IMPointer obj, IMPointer stream)
{
    IMEvent  *ep = IM_EVENT (obj);
    IMStream *sp = IM_STREAM (stream);
    IMUInt32  buf [9];
    IMType    type;

    _im_assert (ep && sp);

    /* Call parent deserialize method */
    if (!__im_event_parent_class->deserialize (obj, stream))
        return FALSE;

    if (im_stream_get_data_type (sp) != IM_TYPE_UINT32_ARRAY ||
        im_stream_get_data_size (sp) != 9 ||
        im_stream_get_uint32_array (sp, buf, 9) == FALSE)
        return FALSE;

    ep->type       = buf [0];
    ep->source     = buf [1];
    ep->dest       = buf [2];
    ep->orig_dest  = buf [3];
    ep->icid       = buf [4];
    ep->timestamp  = (((IMUInt64)buf [5]) | (((IMUInt64)buf [6]) << 32));
    ep->serial     = buf [7];
    ep->observable = ((buf [8] & 1) != 0);
    ep->reply      = ((buf [8] & 2) != 0);

    if (ep->data)
        im_object_unref (ep->data);

    ep->data = 0;

    type = im_stream_get_data_type (sp);
    if (type == IM_TYPE_VALUE_ARRAY) {
        /* Newly created IMValueArray should be owned by the event */
        ep->data = im_object_ref (im_value_array_new ());
        return im_stream_get_object (sp, ep->data);
    } else if (type == IM_TYPE_VOID) {
        return im_stream_get_void (sp);
    }

    return FALSE;
}

static void
__im_event_class_init (IMPointer klass)
{
    IMObjectClass *cp = (IMObjectClass *)klass;

    cp->copy        = __im_event_copy;
    cp->serialize   = __im_event_serialize;
    cp->deserialize = __im_event_deserialize;

    __im_event_parent_class = im_object_class_get_parent (cp);
}

/************* Implementation of public functions ***************/
void
__im_type_register_event ()
{
    static IMTypeInfo im_event_type_info =
    {
        sizeof (IMEventClass),      /**< class_size >**/
        __im_event_class_init,      /**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMEvent),           /**< instance_size >**/
        __im_event_initialize,      /**< instance_init >**/
        __im_event_finalize,        /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_OBJECT,
                                      IM_TYPE_EVENT,
                                      "Event",
                                      &im_event_type_info,
                                      TRUE);
}

IMEvent*
im_event_new (IMUInt32 type, IMBool reply)
{
    IMEvent *event = (IMEvent *)im_object_new (IM_TYPE_EVENT);

    _im_assert (event);

    event->type = type;
    event->reply = reply;

    return event;
}

IMEvent*
im_event_new_full (IMUInt32 type, IMUInt32 src_id, IMUInt32 dest_id,
                   IMUInt32 ic_id, IMBool observable, IMBool reply)
{
    IMEvent *event = (IMEvent *)im_object_new (IM_TYPE_EVENT);

    _im_assert (event);

    event->type = type;
    event->source     = src_id;
    event->dest       = dest_id;
    event->icid       = ic_id;
    event->observable = observable;
    event->reply      = reply;

    return event;
}

void
im_event_clear_data (IMEvent *event)
{
    _im_return_if_fail (IM_IS_EVENT(event));

    if (event->data)
        im_object_unref (event->data);

    event->data = 0;
}

void
im_event_set_type (IMEvent *event, IMUInt32 type)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->type = type;
}

IMUInt32
im_event_get_type (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->type;
}

void
im_event_set_source (IMEvent *event, IMUInt32 src_id)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->source = src_id;
}

IMUInt32
im_event_get_source (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->source;
}

void
im_event_set_destination (IMEvent *event, IMUInt32 dest_id)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->dest = dest_id;
}

IMUInt32
im_event_get_destination (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->dest;
}

void
im_event_set_original_destination (IMEvent *event, IMUInt32 orig_dest_id)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->orig_dest = orig_dest_id;
}

IMUInt32
im_event_get_original_destination (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->orig_dest;
}

void
im_event_set_serial_number (IMEvent *event, IMUInt32 serial)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->serial = serial;
}

IMUInt32
im_event_get_serial_number (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->serial;
}

void
im_event_set_input_context_id (IMEvent *event, IMUInt32 icid)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->icid = icid;
}

IMUInt32
im_event_get_input_context_id (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->icid;
}

void
im_event_update_timestamp (IMEvent *event)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->timestamp = im_get_current_time_in_milliseconds ();
}

void
im_event_set_timestamp (IMEvent *event, IMUInt64 timestamp)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->timestamp = timestamp;
}

IMUInt64
im_event_get_timestamp (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    return event->timestamp;
}

void
im_event_set_observable (IMEvent *event, IMBool observable)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->observable = observable;
}

IMBool
im_event_get_observable (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), FALSE);
    return event->observable;
}

void
im_event_set_is_reply (IMEvent *event, IMBool reply)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    event->reply = reply;
}

IMBool
im_event_get_is_reply (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), FALSE);
    return event->reply;
}

IMSize
im_event_get_n_data (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);

    return event->data ? im_value_array_size (event->data) : 0;
}

IMType
im_event_get_data_type (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), IM_TYPE_INVALID);

    return  (event->data && index < im_value_array_size (event->data)) ?
            im_value_array_get_value_type (event->data, index) :
            IM_TYPE_INVALID;
}

IMType
im_event_get_data (const IMEvent *event, IMSize index, IMValue *value)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), IM_TYPE_INVALID);
    _im_return_val_if_fail (IM_IS_VALUE(value), IM_TYPE_INVALID);

    if (event->data && index < im_value_array_size (event->data))
        return im_value_array_get (event->data, index, value);

    return IM_TYPE_INVALID;
}

void
im_event_append_data (IMEvent *event, IMValue *value)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    _im_return_if_fail (IM_IS_VALUE(value));

    if (!event->data) {
        /* Newly created IMValueArray should be owned by the event */
        event->data = im_object_ref (im_value_array_new ());
    }

    _im_assert (event->data);

    im_value_array_append (event->data, value);
}

void
im_event_set_data (IMEvent *event, IMSize index, IMValue *value)
{
    _im_return_if_fail (IM_IS_EVENT(event));
    _im_return_if_fail (IM_IS_VALUE(value));

    if (!event->data) {
        /* Newly created IMValueArray should be owned by the event */
        event->data = im_object_ref (im_value_array_new ());
    }

    _im_assert (event->data);

    if (index >= im_value_array_size (event->data)) {
        IMSize   i;
        IMSize   n = index - im_value_array_size (event->data) + 1;
        IMValue *tmp = im_value_new ();

        im_object_ref (tmp);

        for (i = 0; i < n; ++i)
            im_value_array_append (event->data, tmp);

        im_object_unref (tmp);
    }

    im_value_array_set (event->data, index, value);
}

IMChar
im_event_get_char (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_CHAR, 0);

    return im_value_array_get_char (event->data, index);
}

IMUChar
im_event_get_uchar (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_UCHAR, 0);

    return im_value_array_get_uchar (event->data, index);
}

IMBool
im_event_get_bool (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), FALSE);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), FALSE);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_BOOL, FALSE);

    return im_value_array_get_bool (event->data, index);
}

IMInt16
im_event_get_int16 (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_INT16, 0);

    return im_value_array_get_int16 (event->data, index);
}

IMUInt16
im_event_get_uint16 (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_UINT16, 0);

    return im_value_array_get_uint16 (event->data, index);
}

IMInt32
im_event_get_int32 (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_INT32, 0);

    return im_value_array_get_int32 (event->data, index);
}

IMUInt32
im_event_get_uint32 (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_UINT32, 0);

    return im_value_array_get_uint32 (event->data, index);
}

IMInt64
im_event_get_int64 (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_INT64, 0);

    return im_value_array_get_int64 (event->data, index);
}

IMUInt64
im_event_get_uint64 (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_UINT64, 0);

    return im_value_array_get_uint64 (event->data, index);
}

IMDouble
im_event_get_double (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_DOUBLE, 0);

    return im_value_array_get_double (event->data, index);
}

const IMChar*
im_event_get_c_string (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_C_STRING, 0);

    return im_value_array_get_c_string (event->data, index);
}

IMChar*
im_event_dup_c_string (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (im_value_array_get_value_type (event->data, index) == IM_TYPE_C_STRING, 0);

    return im_value_array_dup_c_string (event->data, index);
}

IMPointer
im_event_get_object (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (IM_TYPE_IS_OBJECT (im_value_array_get_value_type (event->data, index)), 0);

    return im_value_array_get_object (event->data, index);
}

IMPointer
im_event_dup_object (const IMEvent *event, IMSize index)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), 0);
    _im_return_val_if_fail (event->data && index < im_value_array_size (event->data), 0);
    _im_return_val_if_fail (IM_TYPE_IS_OBJECT (im_value_array_get_value_type (event->data, index)), 0);

    return im_value_array_dup_object (event->data, index);
}

IMBool
im_event_validate (const IMEvent *event)
{
    _im_return_val_if_fail (IM_IS_EVENT(event), FALSE);

    /* TODO */
    return TRUE;
}
/*
vi:ts=4:nowrap:ai:expandtab
*/
